{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 问题：Python 的 super 方法有什么用？\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "面试官想从这道题中考察面试人什么？\n",
    "面向对象编程的用法在面试中是一个必不可少的考点。面向对象编程的主要优点之一是重用。继承是实现继承的机制之一。 在继承中，一个类（通常称为超类）被另一个类（通常称为子类）继承。子类为超类添加了一些属性。Python有一个其它语言所没有的特点，即支持多重继承。以上这些，super函数都在其中发挥着重要的作用。所以，对于这个问题，面试官考察的可能是以下几点，它们的排列由易及难：\n",
    "\n",
    "基本概念（初级）：super函数是干什么用的。\n",
    "编程能力（中级）：怎么使用super函数，写出一个最简单的单继承的代码片段。\n",
    "能否拥有写出高效代码的能力（高级）：了解super函数的优点和如何高效使用它。\n",
    "答案\n",
    "在子类中使用super函数，它会在当前实例的继承树中（MRO列表）搜索下一个基类，把基类方法绑定到当前实例上并调用。\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 拓展\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "super函数是2.2版本中为新式类添加的一个函数，它为我们提供了显式引用父类的便利，可以为子类找到合适的可以调用的基类函数。在必须调用基类函数的地方，这基本上很有用。它返回允许我们通过“super”引用父类的代理对象。\n",
    "\n",
    "如何去理解super呢？\n",
    "\n",
    "这样理解吧，我们有一个父类F，一个子类S，父类有一个实例方法foo，子类S想继承父类的方法foo，同时又想有自己的一些特殊处理。super方法是一个绑定方法，即绑定到对象的方法，为该方法提供对象的上下文，例如所有实例属性。\n",
    "\n",
    "class F:\n",
    "    def foo(self):\n",
    "        print(\"I'm F.foo\")\n",
    "\n",
    "class C(F):\n",
    "    def foo(self):\n",
    "        # 子类如果重写父类方法foo，会直接覆盖父类方法\n",
    "        # 如果我想在这里执行父类的foo方法，该怎么办？\n",
    "        super().foo() # 《====== 答案：调用super().foo()，\n",
    "        print(\"I'm C.foo\")\n",
    "\n",
    ">>> c = C()\n",
    ">>> c.foo()\n",
    "I'm F.foo\n",
    "I'm C.foo\n",
    "Tips: 这个例子super().foo()等同于super(C，self).foo()，就是从C类的上一级中去寻找foo方法。\n",
    "\n",
    "也许有人会觉得，为何不直接指定父类，像这样:\n",
    "\n",
    "class AC(F):\n",
    "    def foo(self):\n",
    "        F.foo(self)\n",
    "        print(\"I'm AC.foo\")\n",
    "\n",
    ">>> ac = AC()\n",
    ">>> ac.foo()\n",
    "I'm F.foo\n",
    "I'm AC.foo\n",
    "这个我们可以暂时这么用，它完全可以按你预想的执行，这个会下面再进行解释。\n",
    "\n",
    "super函数的使用场景：\n",
    "\n",
    "单继承\n",
    "\n",
    "super可用来引用父类而不必显式地指定它们的名称，从而令代码更易维护，向前兼容性。\n",
    "\n",
    "下面是一个最简单的调用super函数的例子：\n",
    "\n",
    "class Parent(object):\n",
    "    def __init__(self):\n",
    "        pass\n",
    "\n",
    "class Child(Parent): # 1\n",
    "    def __init__(self):\n",
    "        # 2\n",
    "        super().__init__() # 3\n",
    "\n",
    "        # 4\n",
    "        self._other_init()\n",
    "#1. 继承父类Parent。\n",
    "#2. 调用super去初始化基类。\n",
    "#3. 没有指明父类的名称，如果需要修改父类，只需要在#1修改，无需在每个引用到父类方法的地方去修改。Python可以在运行中计算获得父类的引用。这也是为什么我们在这里不使用Parent.__init__()的原因。相比于使用调用基类，调用super更为方便。这里面还有另外一个原因，这个会在下面的多重继承的例子中体现出来。\n",
    "#4. 可以再对子类的其它特有的属性初始化或者覆盖某些父类属性。这样就可以既继承了父类的功能, 又要了新的功能。\n",
    "\n",
    "super是通过计算得到的非直接引用。非直接引用是运行时才进行计算，那我们就可以自由地改变计算过程，让它指向其它类。\n",
    "\n",
    "多重继承\n",
    "\n",
    "多重继承是Python所独有的一种特性，这时会有多个基类实现相同的方法，这就有可能可以实现“菱形图”(“diamond problem”)。\n",
    "\n",
    "Class D\n",
    "Class C\n",
    "Class B\n",
    "Class A\n",
    "这个菱形图什么意思呢？在经典类中，如果D去调用foo方法，但是D并没有定义，那么它就去继承B的foo，但是B也没定义，然后它就会沿着去找B的父类A，发现了它定义了，所以直接继承，但是C的foo就完全被忽略掉了。这就是在经典类中采取的深度优先的MRO算法。\n",
    "\n",
    "# 以下代码是在python2.7中执行的，使用到的是经典类\n",
    "class A:\n",
    "    def foo(self):\n",
    "        print \"called A\"\n",
    "\n",
    "class B(A):\n",
    "    pass\n",
    "\n",
    "class C(A):\n",
    "    def foo(self):\n",
    "        print \"called C\"\n",
    "\n",
    "class D(B, C):\n",
    "    pass\n",
    "\n",
    ">>> d = D()\n",
    ">>> d.foo()\n",
    "called A\n",
    "如果我换成新式类（上面的代码直接拷贝到python3中，或在python2把class A换成class A(object)），使用新的MRO算法，即宽带优先算法，如果B中没有定义，会先查看另一个直接父类C是否定义了，这个时候调用foo的打印这是called C.\n",
    "\n",
    "下面我们再进一步了解MRO。\n",
    "\n",
    "class Parent(object):\n",
    "    def __init__(self):\n",
    "        print(\"enter Parent\")\n",
    "        print(\"leave Parent\")\n",
    "\n",
    "class ChildA(Parent):\n",
    "    def __init__(self):\n",
    "        print(\"enter ChildA\")\n",
    "        Parent.__init__(self) # TODO: 替换成 super().__init__()\n",
    "        print(\"leave ChildA\")\n",
    "\n",
    "class ChildB(Parent):\n",
    "    def __init__(self):\n",
    "        print(\"enter ChildB\")\n",
    "        Parent.__init__(self) # TODO: 替换成 super().__init__()\n",
    "        print(\"leave ChildB\")\n",
    "\n",
    "class GrandChild(ChildA, ChildB):\n",
    "    def __init__(self):\n",
    "        print(\"enter GrandChild\")\n",
    "        ChildA.__init__(self) # TODO: 替换成 super().__init__()\n",
    "        ChildB.__init__(self) # TODO: 去掉这行\n",
    "        print(\"leave GrandChild\")\n",
    "\n",
    ">>> child = GrandChild()\n",
    "enter GrandChild\n",
    "enter ChildA\n",
    "enter Parent <= 第一次初始化Parent\n",
    "leave Parent\n",
    "leave ChildA\n",
    "enter ChildB\n",
    "enter Parent <= 第二次初始化Parent\n",
    "leave Parent\n",
    "leave ChildB\n",
    "leave GrandChild\n",
    "上面这个例子，你会发现，父类Parent初始化了两次，但这不是我们想要的，我们只希望，GrandChild继承的基类都只初始化一次。如果我们把调用初始化函数的部分替换为super函数就可以解决这个问题，它会根据一个搜索顺序逐一对父类引用调用方法。\n",
    "\n",
    "下面，我们把上一个代码片段中注解的部分替换成super，再看看打印的顺序。\n",
    "\n",
    ">>> child = GrandChild()\n",
    "enter GrandChild\n",
    "enter ChildA\n",
    "enter ChildB\n",
    "enter Parent\n",
    "leave Parent\n",
    "leave ChildB\n",
    "leave ChildA\n",
    "leave GrandChild\n",
    "以上的打印都是根据MRO的顺序，依次调用各自的__init__。\n",
    "讲到这里，我们看看怎么获得这个方法解析顺序（MRO）。这个序列包含了类本身，再是它的父类，然后才会是父类的父类，一直到所有类的祖先类object。这个序列经过了排序，因此一个类总是出现在它的父类之前，如果有多个父类，它们保持与基类元组相同的顺序。super函数的工作就是将方法调用委托给祖先树中的某个类。但是，在经典类中，可能顺序就不一样了。\n",
    "\n",
    ">>> GrandChild.__mro__\n",
    "(__main__.GrandChild, __main__.ChildA, __main__.ChildB, __main__.Parent, object)\n",
    "Tips: super永远指的都是MRO中的下一个类。这就能很好地解释ChildA类中的super调用的是ChildB的方法，不是Parent的方法。多继承类是通过MRO的方式来保证各个父类的函数被逐一调用，而且保证每个父类函数只调用一次。\n",
    "\n",
    "为什么我们需要知道MRO呢？因为在构建混合类的时候，需要自己去构造一个MRO顺序，否则中间会发生一些意想不到的错误，就像下面这个例子一样。\n",
    "\n",
    "class Rectangle:\n",
    "    def __init__(self, length, width):\n",
    "        self.length = length\n",
    "        self.width = width\n",
    "\n",
    "    def area(self):\n",
    "        return self.length * self.width\n",
    "\n",
    "    def perimeter(self):\n",
    "        return 2 * self.length + 2 * self.width\n",
    "\n",
    "class Square(Rectangle):\n",
    "    def __init__(self, length):\n",
    "        super(Square, self).__init__(length, length)\n",
    "\n",
    "class Triangle:\n",
    "    def __init__(self, base, height):\n",
    "        self.base = base\n",
    "        self.height = height\n",
    "\n",
    "    def area(self):\n",
    "        return 0.5 * self.base * self.height\n",
    "\n",
    "class RightPyramid(Triangle, Square):\n",
    "    def __init__(self, base, slant_height):\n",
    "        self.base = base\n",
    "        self.slant_height = slant_height\n",
    "\n",
    "    def area(self):\n",
    "        base_area = super().area()\n",
    "        perimeter = super().perimeter()\n",
    "        return 0.5 * perimeter * self.slant_height + base_area\n",
    "你去检查这个混合类，看上去好像没有问题。那么，我们测试一些吧：\n",
    "\n",
    ">>> pyramid = RightPyramid(2, 4)\n",
    ">>> pyramid.area()\n",
    "---------------------------------------------------------------------------\n",
    "AttributeError                            Traceback (most recent call last)\n",
    "<ipython-input-44-ca61fb199c11> in <module>\n",
    "----> 1 pyramid.area()\n",
    "\n",
    "<ipython-input-42-ad85c18e273e> in area(self)\n",
    "     28\n",
    "     29     def area(self):\n",
    "---> 30         base_area = super().area()\n",
    "     31         perimeter = super().perimeter()\n",
    "     32         return 0.5 * perimeter * self.slant_height + base_area\n",
    "\n",
    "<ipython-input-42-ad85c18e273e> in area(self)\n",
    "     20\n",
    "     21     def area(self):\n",
    "---> 22         return 0.5 * self.base * self.height\n",
    "     23\n",
    "     24 class RightPyramid(Triangle, Square):\n",
    "\n",
    "AttributeError: 'RightPyramid' object has no attribute 'height'\n",
    "问题出在哪呢？在这一行base_area = super().area()，RightPyramid试图去调用Square的area()，但是它去调用了Triangle的area()，因为在RightPyramid的MRO链中，Triangle排在Square前面，并且这两个类都有area方法，那怎么解决呢？\n",
    "\n",
    "把RightPyramid的父类顺序调整一下，Square在前，Triangle在后。\n",
    "或者把Triangle的area方法重命名一下，这样在搜索基类方法的时候可以跳过Triangle，然后在Square找到area方法并调用。\n",
    "所以，在混合类中使用super可能会出现一些你意想不到的情况，我们需要尽量去遵循一些通用的原则，这里就先简单概括一下：\n",
    "\n",
    "被super()调用的方法需要存在\n",
    "调用者和被调用者需要有匹配的参数\n",
    "每一个方法对象的出现都需要super()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}